-module(lib_map).
-include("map.hrl").

-export([make_all/0,make_erlang_file/2,collect_data/6,to_number/1]).

to_list(Msg) when is_integer(Msg) -> 
    integer_to_list(Msg);
to_list(Msg) when is_float(Msg) -> 
    erlang:float_to_list(Msg,[{decimals, 12}, compact]).

to_number(Msg) when is_list(Msg) ->
	case (catch list_to_integer(Msg)) of
		Value when is_integer(Value) -> Value;
		_ ->
			list_to_float(Msg)
	end;
to_number(Msg) when is_integer(Msg) ->
	Msg;
to_number(Msg) when is_float(Msg) ->
	Msg;
to_number(Msg) when is_binary(Msg) ->
	to_number(binary_to_list(Msg)).	

map_point_to_string(#map_point{x = X,y = Y,z = Z}) -> 	
	"#map_point{x = " ++ to_list(X) ++ 
		",y = " ++ to_list(Y) ++ 
		",z = " ++ to_list(Z) ++ "}".
line_index_to_string({Point1,Point2}) -> "{" ++ to_list(Point1) ++ "," ++ to_list(Point2) ++ "}";
line_index_to_string(_) -> "no".
face_index_to_string({Point1,Point2,Point3}) -> "{" ++ to_list(Point1) ++ "," ++ to_list(Point2) ++ "," ++ to_list(Point3) ++ "}";
face_index_to_string(_) -> "no".
divide_index_to_string({Point1,Point2,Point3}) -> "{" ++ to_list(Point1) ++ "," ++ to_list(Point2) ++ "," ++ to_list(Point3) ++ "}";
divide_index_to_string(_) -> "no".
face_list_to_string(FaceList) -> 
	F = fun(FaceIndex,Str) -> 
				case Str of
					"[" -> Str ++ face_index_to_string(FaceIndex);
					_ -> Str ++ "," ++ face_index_to_string(FaceIndex)
				end
		end,
	lists:foldl(F, "[", FaceList) ++ "]".

make_all() ->
	case file:list_dir("./") of
		{ok,Files} ->
			Fun = fun(FileName) ->
						  case string:right(FileName, 4) of
							  ".obj" -> 
								  make_erlang_file(FileName,erlang:list_to_atom(string:sub_string(FileName, 1, length(FileName) - 4))),
								  io:format("make finish,FileName = ~p~n", [FileName]);
							  _ -> io:format("waring:make_all,error file FileName = ~p~n", [FileName])
						  end
				  end,
			lists:foreach(Fun, Files);
		_R -> io:format("error:make_all,error _R = ~p~n", [_R])
	end.

make_erlang_file(TxtFile,Name)->
	InitInfo = get_info_by_file(TxtFile),
	Create = "scene_" ++ erlang:atom_to_list(Name),
%% 	io:format("create file name = ~p~n",[Create]),
	case file:open(Create ++ ".erl", [write]) of
		{ok, File} ->
			file:write(File, "-module(" ++ Create ++ ").\r\n"),
			file:write(File, "-include(\"common.hrl\").\r\n"),
			file:write(File, "-export([get_point/1,get_line/1,get_face/1,get_divide_info/0,get_divide/1]).\r\n"),
			file:write(File, "\r\n"),
			PointIndexs = dict:fetch_keys(InitInfo#map_info.points),
			FPoint = fun(PointIndex) -> 
							 case dict:find(PointIndex, InitInfo#map_info.points) of
								 {ok,[MapPoint]} ->
									 file:write(File, "get_point(" ++ to_list(PointIndex) ++ ")-> " ++ map_point_to_string(MapPoint) ++ ";\r\n");
								 _ -> skip
							 end
					 end,
			lists:foreach(FPoint, PointIndexs),
			file:write(File, "get_point(_)->no.\r\n"),
			file:write(File, "\r\n"),
			
			LineIndexs = dict:fetch_keys(InitInfo#map_info.lines),
			FLine = fun(LineIndex) -> 
							 case dict:find(LineIndex, InitInfo#map_info.lines) of
								 {ok,[#map_line{point1 = Point1,point2 = Point2,centre_point = CentrePoint,connect_face1 = ConnectFace1,connect_face2 = ConnectFace2}]} ->
									 file:write(File, "get_line(" ++ line_index_to_string(LineIndex) ++ ")->#map_line{point1 = " ++ to_list(Point1) ++ 
													",point2 = " ++ to_list(Point2) ++ 
													",centre_point = " ++ map_point_to_string(CentrePoint) ++ 
													",connect_face1 = " ++ face_index_to_string(ConnectFace1) ++ 
													",connect_face2 = " ++ face_index_to_string(ConnectFace2) ++ 
													"};\r\n");
								 _ -> skip
							 end
					 end,
			lists:foreach(FLine, LineIndexs),
			file:write(File, "get_line(_)->no.\r\n"),
			file:write(File, "\r\n"),
			
			FaceIndexs = dict:fetch_keys(InitInfo#map_info.faces),
			FFace = fun(FaceIndex) -> 
							 case dict:find(FaceIndex, InitInfo#map_info.faces) of
								 {ok,[#map_face{point1 = Point1,point2 = Point2,point3 = Point3,mini_point = MiniPoint,max_point = MaxPoint}]} ->
									 file:write(File, "get_face(" ++ face_index_to_string(FaceIndex) ++ ")->#map_face{point1 = " ++ to_list(Point1) ++ 
													",point2 = " ++ to_list(Point2) ++ 
													",point3 = " ++ to_list(Point3) ++ 
													",mini_point = " ++ map_point_to_string(MiniPoint) ++ 
													",max_point = " ++ map_point_to_string(MaxPoint) ++ 
													"};\r\n");
								 _ -> skip
							 end
					 end,
			lists:foreach(FFace, FaceIndexs),
			file:write(File, "get_face(_)->no.\r\n"),
			file:write(File, "\r\n"),
							
			{PointFrom,PointTo,PerPoint,DivideDict} = InitInfo#map_info.dict,
			file:write(File, "get_divide_info()->{" ++ map_point_to_string(PointFrom) ++ 
													"," ++ map_point_to_string(PointTo) ++ 
													"," ++ map_point_to_string(PerPoint) ++ 
													"}.\r\n"),
			file:write(File, "\r\n"),	
			
			DivideIndexs = dict:fetch_keys(DivideDict),
			FDivide = fun(DivideIndex) -> 
							 case dict:find(DivideIndex, DivideDict) of
								 {ok,[FaceList]} ->
									 file:write(File, "get_divide(" ++ divide_index_to_string(DivideIndex) ++ ")-> " ++ face_list_to_string(FaceList) ++ 
													";\r\n");
								 _ -> skip
							 end
					 end,
			lists:foreach(FDivide, DivideIndexs),
			file:write(File, "get_divide(_)->no.\r\n"),
			file:write(File, "\r\n"),
			
			file:close(File);			
		_R -> 
			io:format("create file _R = ~p~n",[_R]),
			skip
	end.

get_info_by_file(File) ->
	init_point_index(),
	
	InitInfo = #map_info{points = dict:new(),lines = dict:new(),faces = dict:new()},
	InitInfo1 = case file:open(File,read) of
					{ok,FD} -> 
						Readed = read_file(InitInfo,FD),
						file:close(FD),
						Readed;
					_ -> {}
				end,
	InitInfo2 = fill_face(InitInfo1),
	InitInfo3 = remove_no_connect_line(InitInfo2),
	DivideInfo = divide_face(InitInfo3,#map_point{x = 20, y = 1, z = 20}),
	InitInfo3#map_info{dict = DivideInfo}.

get_point(#map_info{points = Points},Index) ->
	case dict:find(Index, Points) of
		{ok,[Point]} -> Point;
		_ -> {}
	end.
get_line(#map_info{lines = Lines},Index) ->
	case dict:find(Index, Lines) of
		{ok,[Line]} -> Line;
		_ -> {}
	end.

get_face(#map_info{faces = Faces},Index) ->
	case dict:find(Index, Faces) of
		{ok,[Face]} -> Face;
		_ -> {}
	end.

init_point_index() -> put(init_point_index,1).
get_point_index() -> Index = get(init_point_index),put(init_point_index,Index + 1),Index.

make_face_index(P1,P2,P3) when P1 == P2 orelse P2 == P3 orelse P1 == P3 -> error;
make_face_index(P1,P2,P3) when P1 < P2 andalso P2 < P3 -> {P1,P2,P3};
make_face_index(P1,P2,P3) when P1 > P2 -> make_face_index(P2,P1,P3);
make_face_index(P1,P2,P3) when P2 > P3 -> make_face_index(P1,P3,P2);
make_face_index(_,_,_) -> error.

make_line_index(P1,P2) when P1 == P2 -> error;
make_line_index(P1,P2) when P1 < P2 -> {P1,P2};
make_line_index(P1,P2) when P1 > P2 -> make_line_index(P2,P1);
make_line_index(_,_) -> error.


mini_point(#map_point{x = X1,y = Y1,z = Z1},#map_point{x = X2,y = Y2,z = Z2}) -> #map_point{x = min(X1,X2),
																							y = min(Y1,Y2),
																							z = min(Z1,Z2)}.
max_point(#map_point{x = X1,y = Y1,z = Z1},#map_point{x = X2,y = Y2,z = Z2}) -> #map_point{x = max(X1,X2),
																						   y = max(Y1,Y2),
																						   z = max(Z1,Z2)}.
centre_point(#map_point{x = X1,y = Y1,z = Z1},#map_point{x = X2,y = Y2,z = Z2}) -> #map_point{x = (X1 + X2) / 2,
																							  y = (Y1 + Y2) / 2,
																							  z = (Z1 + Z2) / 2}.

divide_face(InitInfo = #map_info{points = Points,faces = Faces},DivideData) ->
	PointIndexs = dict:fetch_keys(Points),
	FunGetDis = fun(PointIndex,{MiniPoint,MaxPoint}) ->
						Point = get_point(InitInfo,PointIndex),
						if
							MaxPoint == null -> {Point,Point};
							true -> {mini_point(MiniPoint,Point),max_point(MaxPoint,Point)}
						end
				end,
	{PointFrom1,PointTo1} = lists:foldl(FunGetDis, {null,null}, PointIndexs),
	{PointFrom,PointTo,PerPoint} = check_divide_point(PointFrom1,PointTo1,DivideData),
	
	FaceIndexs = dict:fetch_keys(Faces),
	DivideDict = dict:new(),
	FunAdd = fun(DivideIndex,{FaceIndex,ThisOldDivideDict}) ->
					 case dict:find(DivideIndex, ThisOldDivideDict) of
						 {ok,[ThisOldDivide]} ->
							 ThisOldDivideDict1 = dict:erase(DivideIndex, ThisOldDivideDict),
							 {FaceIndex,dict:append(DivideIndex, ThisOldDivide ++ [FaceIndex], ThisOldDivideDict1)};
						 _ -> {FaceIndex,dict:append(DivideIndex, [FaceIndex], ThisOldDivideDict)}
					 end
			 end,
	Fun = fun(FaceIndex,OldDivideDict) ->
				  Face = get_face(InitInfo,FaceIndex),
				  DivideIndexs = collect_index(Face#map_face.mini_point,Face#map_face.max_point,PointFrom,PointTo,PerPoint),
%% 				  io:format("FaceIndex = ~p,DivideIndexs = ~p~n",[FaceIndex,DivideIndexs]),
				  {_,OldDivideDict1} = lists:foldl(FunAdd, {FaceIndex,OldDivideDict}, DivideIndexs),
				  OldDivideDict1
		  end,
	NewDivideDict = lists:foldl(Fun, DivideDict, FaceIndexs),
				  
	{PointFrom,PointTo,PerPoint,NewDivideDict}.

check_divide_point(PointFrom,PointTo,DivideData) -> 
	{XFrom,XTo,PerX} = check_divide_data(PointFrom#map_point.x,PointTo#map_point.x,DivideData#map_point.x),
	{YFrom,YTo,PerY} = check_divide_data(PointFrom#map_point.y,PointTo#map_point.y,DivideData#map_point.y),
	{ZFrom,ZTo,PerZ} = check_divide_data(PointFrom#map_point.z,PointTo#map_point.z,DivideData#map_point.z),
	{#map_point{x = XFrom,y = YFrom,z = ZFrom},#map_point{x = XTo,y = YTo,z = ZTo},#map_point{x = PerX,y = PerY,z = PerZ}}.
check_divide_data(From,To,Num) when Num =< 0 -> check_divide_data(From,To,1);
check_divide_data(From,To,Num) -> {From - 0.5 ,To + 0.5 ,(To - From + 1) / Num}.

remove_no_connect_line(InitInfo = #map_info{lines = Lines}) ->
	LineIndexs = dict:fetch_keys(Lines),
	Fun = fun(LineIndex) ->
				  case get_line(InitInfo,LineIndex) of
					  {} -> true;
					  #map_line{connect_face1 = null} -> true;
					  _ -> false
				  end
		  end,
	NeedDelLines = lists:filter(Fun, LineIndexs),
	FunDel = fun(LineIndex,OldLines) -> dict:erase(LineIndex, OldLines) end,
	NewLines = lists:foldl(FunDel, Lines, NeedDelLines),
	InitInfo#map_info{lines = NewLines}.

fill_face(InitInfo = #map_info{faces = Faces,lines = Lines}) ->
	FaceIndexs = dict:fetch_keys(Faces),
	FunLine = fun(NewLine = #map_line{point1 = P1,point2 = P2},OldLines) ->
					  LineIndex = make_line_index(P1,P2),
					  case dict:find(LineIndex, OldLines) of
						  {ok,[FindLine]} ->
							  if
								  FindLine#map_line.connect_face1 == NewLine#map_line.connect_face1 -> 
									  OldLines;
								  FindLine#map_line.connect_face2 == NewLine#map_line.connect_face1 -> 
									  OldLines;
								  FindLine#map_line.connect_face1 == null -> 
									  OldLines1 = dict:erase(LineIndex, OldLines),
									  NewFindLine = FindLine#map_line{connect_face1 = NewLine#map_line.connect_face1},
									  dict:append(LineIndex, NewFindLine, OldLines1);
								  FindLine#map_line.connect_face2 == null ->
									  OldLines1 = dict:erase(LineIndex, OldLines),
									  NewFindLine = FindLine#map_line{connect_face2 = NewLine#map_line.connect_face1},
									  dict:append(LineIndex, NewFindLine, OldLines1);
								  true -> {error,line_has_max_connect_face,LineIndex,FindLine#map_line.connect_face1,FindLine#map_line.connect_face2,NewLine#map_line.connect_face1}
							  end;							  
						  _ -> dict:append(LineIndex, NewLine, OldLines)
					  end
			  end,
	
	FunFill = fun(FaceIndex,{OldFaces,OldLines}) ->
					  case dict:find(FaceIndex, OldFaces) of
						  {ok,[Face]} ->
							  case fill_face(InitInfo,FaceIndex,Face) of
								  {NewFace,GetNewLines} ->
									  OldLines1 = lists:foldl(FunLine, OldLines, GetNewLines),
									  OldFaces1 = dict:erase(FaceIndex, OldFaces),
									  {dict:append(FaceIndex, NewFace, OldFaces1),OldLines1};
								  E -> {error,fill_face_error,E}
							  end;
						  _ -> {error,can_not_find_face,FaceIndex}
					  end
			  end,
	{NewFaces,NewLines} = lists:foldl(FunFill, {Faces,Lines}, FaceIndexs),
	InitInfo#map_info{faces = NewFaces,lines = NewLines}.

fill_face(Info,FaceIndex,Face = #map_face{point1 = P1,point2 = P2,point3 = P3}) ->
	NewFace = Face#map_face{mini_point = mini_point(mini_point(get_point(Info,P1),get_point(Info,P2)),get_point(Info,P3)),max_point = max_point(max_point(get_point(Info,P1),get_point(Info,P2)),get_point(Info,P3))},
	Line1 = #map_line{point1 = P1,point2 = P2,centre_point = centre_point(get_point(Info,P1),get_point(Info,P2)),connect_face1 = FaceIndex},
	Line2 = #map_line{point1 = P2,point2 = P3,centre_point = centre_point(get_point(Info,P2),get_point(Info,P3)),connect_face1 = FaceIndex},
	Line3 = #map_line{point1 = P1,point2 = P3,centre_point = centre_point(get_point(Info,P1),get_point(Info,P3)),connect_face1 = FaceIndex},
	{NewFace,[Line1,Line2,Line3]}.	

read_file(InitInfo = #map_info{points = Points,faces = Faces},FD) ->
	case file:read_line(FD) of
		{ok,Str} ->
			case read_str(Str) of
				Point = #map_point{} ->
					Index = get_point_index(),
					read_file(InitInfo#map_info{points = dict:append(Index, Point, Points)},FD);
				Face = #map_face{point1 = P1,point2 = P2,point3 = P3} ->
					case make_face_index(P1,P2,P3) of
						error -> {error,get_pint_index_error};
						Index ->
							read_file(InitInfo#map_info{faces = dict:append(Index, Face, Faces)},FD)
					end;
				_ -> read_file(InitInfo,FD)
			end;
		eof -> InitInfo;
		R -> {error,read_file_error,R}
	end.

read_str(Str) ->
	Strs = string:tokens(Str, " \r\n"),
	case Strs of
		["v",SX,SY,SZ] -> #map_point{x = -1 * to_number(SX),y = to_number(SY),z = to_number(SZ)};
		["f",SV1,SV2,SV3] -> #map_face{point1 = to_number(SV1),point2 = to_number(SV2),point3 = to_number(SV3)};
		_ -> {error,unknow_str,Str}
	end.



collect_index(MyMini,MyMax,DFrom,DTo,DPer) ->
	XS = collect_data(MyMini#map_point.x,MyMax#map_point.x,DFrom#map_point.x,DTo#map_point.x,DPer#map_point.x,[]),
	YS = collect_data(MyMini#map_point.y,MyMax#map_point.y,DFrom#map_point.y,DTo#map_point.y,DPer#map_point.y,[]),
	ZS = collect_data(MyMini#map_point.z,MyMax#map_point.z,DFrom#map_point.z,DTo#map_point.z,DPer#map_point.z,[]),
	
%% 	io:format("XS = ~p,data=~p~n",[XS,{MyMini#map_point.x,MyMax#map_point.x,DFrom#map_point.x,DTo#map_point.x,DPer#map_point.x,[]}]),
	FunZ = fun(Z,{X,Y,Has}) -> {X,Y,Has ++ [{X,Y,Z}]} end,
	FunY = fun(Y,{X,ZS,Has}) -> 
				   {_,_,Finds} = lists:foldl(FunZ, {X,Y,Has}, ZS),
				   {X,ZS,Finds}
		   end,
	FunX = fun(X,{YS,ZS,Has}) -> 
				   {_,_,Finds} = lists:foldl(FunY, {X,ZS,Has}, YS),
				   {YS,ZS,Finds}
		   end,
	{_,_,Finds} = lists:foldl(FunX, {YS,ZS,[]}, XS),
	Finds.

collect_data(_MyMini,_MyMax,_DFrom,_DTo,DPer,_Finds) when DPer == 0 -> [0];
collect_data(_MyMini,_MyMax,DFrom,DTo,_DPer,_Finds) when DFrom == DTo -> [0];
collect_data(MyMini,_MyMax,DFrom,_DTo,_DPer,Finds) when MyMini < DFrom -> Finds;
collect_data(_MyMini,MyMax,_DFrom,DTo,_DPer,Finds) when MyMax > DTo -> Finds;
collect_data(MyMini,MyMax,DFrom,DTo,DPer,[]) when MyMini + DPer > MyMax ->
	IndexMinCeil = get_data_index_ceil(MyMini,DFrom,DTo,DPer),
	IndexMinFloor = get_data_index_floor(MyMini,DFrom,DTo,DPer),
	IndexMaxFloor = get_data_index_floor(MyMax,DFrom,DTo,DPer),
	Finds1 = if
				 IndexMinCeil == IndexMinFloor -> [IndexMinCeil];
				 true -> [IndexMinCeil,IndexMinFloor]
			 end,
	if
		IndexMinFloor == IndexMaxFloor -> Finds1;
		true -> Finds1 ++ [IndexMaxFloor]
	end;
collect_data(MyMini,MyMax,DFrom,DTo,DPer,Finds) when MyMini + DPer > MyMax ->
	IndexMin = get_data_index_floor(MyMini,DFrom,DTo,DPer),
	IndexMax = get_data_index_floor(MyMax,DFrom,DTo,DPer),
	if
		IndexMin == IndexMax -> (Finds -- [IndexMin]) ++ [IndexMin];
		true -> (Finds -- [IndexMin,IndexMax]) ++ [IndexMin,IndexMax]
	end;
collect_data(MyMini,MyMax,DFrom,DTo,DPer,[]) -> 
	IndexMinCeil = get_data_index_ceil(MyMini,DFrom,DTo,DPer),
	IndexMinFloor = get_data_index_floor(MyMini,DFrom,DTo,DPer),
%% 	io:format("IndexMinCeil = ~p,IndexMinFloor=~p,data=~p~n", [IndexMinCeil,IndexMinFloor,{MyMini,MyMax,DFrom,DTo,DPer}]),
	if
		IndexMinCeil == IndexMinFloor -> collect_data(MyMini + DPer,MyMax,DFrom,DTo,DPer,[IndexMinCeil]);
		true -> collect_data(MyMini + DPer,MyMax,DFrom,DTo,DPer,[IndexMinCeil,IndexMinFloor])
	end;	
collect_data(MyMini,MyMax,DFrom,DTo,DPer,Finds) -> 
%% 	io:format("IndexMinFloor = ~p,data=~p~n", [get_data_index_floor(MyMini,DFrom,DTo,DPer),{MyMini,MyMax,DFrom,DTo,DPer}]),
	collect_data(MyMini + DPer,MyMax,DFrom,DTo,DPer,(Finds -- [get_data_index_floor(MyMini,DFrom,DTo,DPer)]) ++ [get_data_index_floor(MyMini,DFrom,DTo,DPer)]).

	
ceil(N) ->
    T = trunc(N),
    case N == T of
        true  -> T;
        false -> 1 + T
    end.

floor(X) ->
    T = trunc(X),
    case (X < T) of
        true -> T - 1;
        _ -> T
    end.

get_data_index_floor(_Data,_From,_To,0) -> 0;
get_data_index_floor(_Data,_From,_To,0.0) -> 0;
get_data_index_floor(Data,From,_To,Per) -> 
	floor((Data - From) / Per).

get_data_index_ceil(_Data,_From,_To,0) -> 0;
get_data_index_ceil(_Data,_From,_To,0.0) -> 0;
get_data_index_ceil(Data,From,_To,Per) -> 
	Value = ceil((Data - From) / Per) - 1,
	if
		Value < 0 -> 0;
		true -> Value
	end.